package framework

import (
	"MoSkeleton/framework/core"
	"MoSkeleton/framework/core/config"
	"MoSkeleton/framework/core/config/properties"
	"MoSkeleton/framework/core/consts"
	"MoSkeleton/framework/core/mologger"
	"MoSkeleton/framework/core/translater"
	"MoSkeleton/framework/external_type"
	"MoSkeleton/framework/mo_gin"
	"MoSkeleton/framework/mo_gorm"
	"MoSkeleton/framework/mo_redis"
	"MoSkeleton/framework/mo_redis/hooks"
	"MoSkeleton/framework/utils"
	"context"
	"errors"
	"github.com/allegro/bigcache/v3"
	"github.com/bwmarrin/snowflake"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"path/filepath"
	"time"

	_ "MoSkeleton/framework/internal"
)

type ConfigContainer struct {
	MoLoggerConfig *config.DefaultConfigOper
	GinConfig      *mo_gin.MoGinConfig

	BootstrapConfigOper  config.ConfigOper
	BootstrapProperty    *properties.BootstrapProperties
	AppConfigOper        config.ConfigOper
	AppProperty          *properties.App
	AppStorageConfigOper config.ConfigOper
	AppStorageProperty   *properties.AppStorage
}

func NewConfigContainer() (*ConfigContainer, error) {
	var c = &ConfigContainer{}
	if err := c.initBootstrapConfig(); err != nil {
		return nil, err
	}
	if err := c.initAppConfig(); err != nil {
		return nil, err
	}
	if err := c.initMoLogger(); err != nil {
		return nil, err
	}
	if err := c.initSnowflake(); err != nil {
		return nil, err
	}
	Translater = translater.NewOnce(WorkDir)
	exportImportVars()
	return c, nil
}

func (c *ConfigContainer) initBootstrapConfig() (err error) {
	c.BootstrapConfigOper, err = config.ConfigOperManagerLoaderInst.LoadBootstrapConfig()
	if err != nil {
		return
	}
	c.BootstrapProperty = new(properties.BootstrapProperties)
	if err = c.BootstrapConfigOper.Unmarshal(c.BootstrapProperty); err != nil {
		return
	}
	MoLogger.Debug("", zap.String("config-type", c.BootstrapProperty.Sys.ConfigType))
	translater.Profile = &c.BootstrapProperty.Sys.Profile
	return nil
}
func (c *ConfigContainer) initAppConfig() (err error) {
	c.AppConfigOper, err = config.ConfigOperManagerLoaderInst.LoadApplicationConfig(c.BootstrapProperty)
	if err != nil {
		return
	}
	c.AppProperty = new(properties.App)
	if err = c.AppConfigOper.Unmarshal(c.AppProperty); err != nil {
		return
	}

	c.AppStorageConfigOper, err = config.ConfigOperManagerLoaderInst.LoadApplicationStorageConfig(c.BootstrapProperty)
	if err != nil {
		return
	}
	c.AppStorageProperty = new(properties.AppStorage)
	if err = c.AppStorageConfigOper.Unmarshal(c.AppStorageProperty); err != nil {
		return
	}
	return nil
}
func (c *ConfigContainer) OnInitSystem() error {
	if err := c.initLocalCache(); err != nil {
		return err
	}
	if err := c.initRedis(); err != nil {
		return err
	}
	if err := c.initMoDb(); err != nil {
		return err
	}
	if err := c.initGin(); err != nil {
		return err
	}
	InitContainerInst.Register(core.InitSys_Order+100, external_type.PasswordInit())
	return nil
}

func (c *ConfigContainer) initRedis() error {
	redisProperty := c.AppStorageProperty.App.RedisProperty
	if !redisProperty.Enable {
		MoLogger.Info("redis is disabled by config")
		return nil
	}
	switch redisProperty.Mode {
	case consts.RedisMode_Standalone:
		MoRedis = redis.NewClient(&redisProperty.Standalone)
	case consts.RedisMode_Sentinel:
		MoRedis = redis.NewFailoverClusterClient(&redisProperty.Sentinel)
	case consts.RedisMode_Cluster:
		MoRedis = redis.NewClusterClient(&redisProperty.Cluster)
	case consts.RedisMode_Shard:
		MoRedis = redis.NewRing(&redisProperty.Shard)
	default:
		MoRedis = redis.NewClient(&redisProperty.Standalone)
	}
	if MoRedis != nil {
		pong, err := MoRedis.Ping(context.Background()).Result()
		if err != nil {
			MoLogger.Error("redis connect ping failed, err:", zap.Error(err))
			return err
		} else {
			MoLogger.Info("redis connect ping response:", zap.String("pong", pong))
		}
	} else {
		return errors.New("redis is nil")
	}
	MoLogger.Debug("redis add key prefix hood")
	mo_redis.RedisProperty = &redisProperty
	MoRedis.AddHook(hooks.KeyPrefix{})
	return nil
}

func (c *ConfigContainer) initSnowflake() error {
	var err error
	snowflakeProperty := c.AppProperty.App.Snowflake
	nodeId := snowflakeProperty.NodeId
	if nodeId == 0 {
		nodeId = 1
	}
	layout := "2006-01-02 15:04:05"
	epochValue := snowflakeProperty.EPochTime
	if epochValue == "" {
		epochValue = "2024-01-01 00:00:00"
	}
	t, err := time.Parse(layout, epochValue)
	if err != nil {
		return err
	}
	snowflake.Epoch = t.Unix()
	SnowflakeNode, err = snowflake.NewNode(nodeId)
	return err
}

func (c *ConfigContainer) initLocalCache() error {
	verbose := c.BootstrapProperty.Sys.Profile.Mode != consts.ReleaseMode
	cacheConfig := bigcache.Config{
		// number of shards (must be a power of 2)
		Shards: 1024,

		// time after which entry can be evicted
		LifeWindow: 2 * time.Hour,

		// Interval between removing expired entries (clean up).
		// If set to <= 0 then no action is performed.
		// Setting to < 1 second is counterproductive — bigcache has a one second resolution.
		CleanWindow: 10 * time.Minute,

		// rps * lifeWindow, used only in initial memory allocation
		MaxEntriesInWindow: 1000 * 10 * 60,

		// max entry size in bytes, used only in initial memory allocation
		MaxEntrySize: 100,

		// prints information about additional memory allocation
		Verbose: verbose,

		// cache will not allocate more memory than this limit, value in MB
		// if value is reached then the oldest entries can be overridden for the new ones
		// 0 value means no size limit
		HardMaxCacheSize: 8192,

		// callback fired when the oldest entry is removed because of its expiration time or no space left
		// for the new entry, or because delete was called. A bitmask representing the reason will be returned.
		// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
		OnRemove: nil,

		// OnRemoveWithReason is a callback fired when the oldest entry is removed because of its expiration time or no space left
		// for the new entry, or because delete was called. A constant representing the reason will be passed through.
		// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
		// Ignored if OnRemove is specified.
		OnRemoveWithReason: nil,
	}
	var err error
	LocalCache, err = bigcache.New(context.Background(), cacheConfig)
	return err
}

// gin
func (c *ConfigContainer) initGin() error {
	mo_gin.Translater = Translater
	c.GinConfig = new(mo_gin.MoGinConfig)
	mo_gin.MoLogger = MoLogger
	var err error
	if GinRouter, err = c.GinConfig.InitGin(); err != nil {
		return err
	}
	return nil
}

// gorm
func (c *ConfigContainer) initMoDb() error {
	if mo_gorm.PersistentDb.DbType == "" {
		return errors.New("MoDb is not configured, please check the configuration file")
	}
	var err error
	if MO_DBORM, err = mo_gorm.GenDbByPersistentDb(mo_gorm.PersistentDb, false); err != nil {
		return err
	}
	return nil
}

func exportImportVars() {
	mo_gorm.MoLogger = MoLogger
	mo_gorm.SnowflakeNode = SnowflakeNode
	utils.MoLogger = MoLogger
	utils.Translater = Translater
	utils.WorkDir = WorkDir
}

// logger
func (c *ConfigContainer) initMoLogger() error {
	configFile := filepath.Join(WorkDir, consts.ConfigPath, "zap-log.yaml")
	c.MoLoggerConfig = config.NewByFile([]string{}, configFile).(*config.DefaultConfigOper)
	zapCoat := properties.ZapCoat{}
	if err := c.MoLoggerConfig.Unmarshal(&zapCoat); err != nil {
		return err
	}
	MoLogger = c.newZap(zapCoat.Zap)
	zap.ReplaceGlobals(MoLogger)
	return nil
}

// Zap 获取 zap.Logger
func (c *ConfigContainer) newZap(zapStruct properties.Zap) (logger *zap.Logger) {
	levels := zapStruct.Levels()
	length := len(levels)
	cores := make([]zapcore.Core, 0, length)
	for i := 0; i < length; i++ {
		core := mologger.NewZapCore(levels[i], zapStruct)
		cores = append(cores, core)
	}
	logger = zap.New(zapcore.NewTee(cores...))
	if zapStruct.ShowLine {
		logger = logger.WithOptions(zap.AddCaller())
	}
	return logger
}
